package org.zoomdev.zoom.dao.transaction;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.zoomdev.zoom.dao.DaoException;
import org.zoomdev.zoom.dao.modules.CoreDaoModule;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

public final class Transactions {


    private static final Logger log = LoggerFactory.getLogger(CoreDaoModule.NAME);
    private static final ThreadLocal<Transactions> threadLocal = new ThreadLocal<>();
    private final int level;
    private final List<Transaction> transactions = new ArrayList<>();
    private int refCount;

    public Transactions(int level) {
        this.level = level;
        this.refCount = 1;
    }

    public static void executeTrans(Runnable runnable) {
        executeTrans(Trans.TRANSACTION_READ_COMMITTED, runnable);
    }

    public static void executeTrans(int level, Runnable runnable) {
        try {
            assert (runnable != null);
            begin(level);
            runnable.run();
            commit();
        } catch (Throwable e) {
            rollback();
            if (e instanceof DaoException) {
                throw (DaoException) e;
            }
            throw new DaoException(e);
        }

    }

    public static void begin(int level) {
        Transactions transactions = get();
        if (transactions == null) {
            transactions = new Transactions(level);
            threadLocal.set(transactions);
        } else {
            transactions.addRefCount();
        }
    }

    public static void commit() {
        Transactions transactions = get();
        if (transactions != null) {
            if (0 == transactions.subRefCount()) {
                try {
                    transactions._commit();
                } finally {
                    remove();
                }
            }
        }
    }

    public static void rollback() {
        Transactions transactions = get();
        if (transactions != null) {
            if (0 == transactions.subRefCount()) {
                try {
                    transactions._rollback();
                } finally {
                    remove();
                }
            }
        }
    }

    public static Transactions get() {
        return threadLocal.get();
    }

    public void addRefCount() {
        ++refCount;
    }

    public int subRefCount() {
        return --refCount;
    }

    private void _commit() {

        try {
            for (Transaction transaction : transactions) {
                try {
                    transaction.commit();
                } catch (Throwable e) {
                    //失败
                    log.error("Error while commit", e);
                    throw new DaoException(e);
                }
            }
        } finally {
            clean();
        }

    }

    public static void remove() {
        threadLocal.remove();
    }

    private void _rollback() {
        try {
            for (Transaction transaction : transactions) {
                try {
                    transaction.rollback();
                } catch (Throwable e) {
                    log.error("Error while rollback", e);
                    throw new DaoException(e);
                }

            }
        } finally {
            clean();
        }

    }

    private void clean() {
        for (Transaction transaction : transactions) {
            transaction.clean();
        }
        transactions.clear();
    }

    public static void releaseConnection(Connection connection) {
        Transactions transactions = get();
        if (transactions == null) {
            try {
                connection.close();
            } catch (Throwable e) {
                log.error("关闭连接失败", e);
            }
        }
    }

    public static Connection getConnection(DataSource dataSource) {
        try {
            Transactions transactions = get();
            if (transactions == null) {
                return dataSource.getConnection();
            }
            return transactions.openConnection(dataSource);
        } catch (SQLException e) {
            throw new DaoException("获取数据库连接失败", e);
        }

    }

    public Connection openConnection(DataSource dataSource) throws SQLException {
        final List<Transaction> transactions = this.transactions;

        for (Transaction transaction : transactions) {
            if (transaction.dataSource == dataSource) {
                return transaction.getConnection();
            }
        }
        Transaction transaction = new Transaction(level, dataSource);
        transactions.add(transaction);
        return transaction.getConnection();
    }
}
